/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import robocup.component.actionfactories.AttentionToActionFactory;
import robocup.component.actionfactories.TurnNeckActionFactory;
import robocup.component.actions.AttentionToAction;
import robocup.component.actions.ChangeViewModeAction;
import robocup.component.actions.InitAction;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.Info;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.connection.TeamSoccerScopeMediator;
import robocup.parser9.ParseException;
import robocup.parser9.RobocupClientParser;

/**
 * RobocupClient - main class for an agent, creates and initializes all
 * important objects like the worldmodel, parser, tactic and action.
 */
public class RobocupAgent extends AbstractAgent {

    // ~ Object Variables
    // ........................................................
    /**
     * These constants are used to read the config file. each agent -
     * configuartion should declare a property of the listed id with its own
     * value. If a value was detected, the defaut values are replaced
     */
    public static final String GUI_CONNECT = "gui-connect";
    public static final String STATES = "statelist";
    /**
     * The agents tricot number, given by the server after
     * initialization.
     */
    protected int number;
    /**
     * Should the players world-model be connected to the gui?
     * If this is the case for at least one agent, the GUI is started
     * automatically, and the agents world-model is posted each cycle
     * to the GUI (Monitor).
     */
    protected boolean useGui;
    /**
     * A flag which enables to switch off the tactic, if desired.
     */
    protected boolean useTactic = true;
    /**
     * The list of states, that should be instantiated for that agent.
     * These must be specified in the config-file, which is given to
     * the agent as property-object on startup.
     */
    protected String[] states;
    /**
     * The synchro determines, when the robocup-agent should send the
     * actions, which were determined by the tactic.
     */
    private Synchro synchro;
    /**
     * The prophet is a container for all situations, which are able
     * to infer knowledge out of the world-model, e.g. the next
     * ball-interception point.
     */
    private Prophet prophet;
    /**
     * The world-model captures the knowldege of the agent, which is
     * directly inferable from his perceptions.
     */
    private WorldModel world;
    /**
     * The tactic is responsible for the determination of an action
     * in a current situation (represented by the world-model and the
     * prophet within the agent).
     */
    private StateTactic tactic;

    // ~ Constructors
    /**
     * Creates a new instance of RobocupAgent, which stands for a player in the
     * robocup environment. Tthis class also holds the main-method, which is
     * used to start the agents/team.
     *
     * @param props -
     *            contains all necessary data for initializing the agent
     */
    public RobocupAgent(Properties props) {
        // read props
        super(props);
    }

    /**
     * This method overrrides the method from AbstractAgent, and initializes the
     * subclass dependent components. These are the synchro, the worldmodel, the
     * parser, the prophet, the action and the tactic.
     */
    @Override
    protected void initComponents() {
        this.synchro = new Synchro(this);
        this.prophet = new Prophet(this);
        this.world = new WorldModel(this);
        this.parser = new RobocupClientParser(new StringReader(""), this);
        this.prophet.init();
        this.tactic = new StateTactic(this, this.prophet);
    }

    /**
     * This method connects the Agent to the SoccerServer, and will return true,
     * if the Agent is connected afterwards. This is done by sending an
     * <code>InitAction</code> to the servers listening port (usually 6000).
     * After that, the server responses with initialization messages. These
     * were forwarded to the world-model, such that initialization can be
     * finished (e.g. the tricot number is contained in that message).
     * Additionally, the clang-version is communicated, and, if the
     * <code>useGui</code> is set, the agent is registered by the monitor.
     *
     * @return true, if connection succeeds, else false
     */
    @Override
    protected boolean initProtocol() {
        try {
            // first connect
            String init = new InitAction(this.mode, this.teamName).toString();
            this.connection.send(init);

            Info info = null;
            String rcv = null;
            while (true) {
                rcv = this.connection.receive();
                if (rcv == null) {
                    System.out.println("Error: Server does not respond in time!");
                    return false;
                }
                info = this.parser.parseCommand(rcv);
                int type = info.getInfoType();
                informWorld(info);

                if (type == Info.TYPE_INIT) {
                    this.tactic.receivedInitInfo();
                    break; // leave the loop
                }

            }

            connection.send("(clang (ver 7 8))");
            System.out.println("[RobocupAgent] Agent " + this.number + " successfully connected!");
            if (this.useGui) {
                // init gui
                TeamSoccerScopeMediator.soccerServerConnectionDefaultHost(this.serverHost);
                TeamSoccerScopeMediator.registerPlayerBean(this);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (ParseException p) {
            p.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * This method overrides the abstract method from AbstractAgent, which is
     * invoked always when an info is received from the soccerserver.
     * Note that this method isn't called, before initialization (<code>
     * initProtocol()</code>) isn't complete.<br><br>
     *
     * The synchro is the component, which decides how to react on infos.
     * While they all are forwarded to the world model, not all are used
     * to invoke a tactical decision process.
     *
     * @param info - the received and parsed information (visual, acustic,
     * sensebody or parameter)
     */
    @Override
    protected void processInfo(Info info) {
        informSynchro(info);

        // world
        if (!this.synchro.is2ndVI() || info.getInfoType() == Info.TYPE_AURAL_PSI || info.getInfoType() == Info.TYPE_AURAL) {
            informWorld(info);
        } else {
            // we already got the VI this cycle
            // which means that this info has no new
            // information
            // --> only insert it into memory, don't
            // update wm
            this.world.getLongTermMemory().insertInfo(info);
        }

        
        // tactic + gui
        // gui gets only informed if the info is used
        // for calculating an action
        if (this.useTactic) {
            // BS
            if (info.getInfoType() == Info.TYPE_SENSE && this.synchro.calculateBSAction()) {
                // calculate action only in special
                // cases (see synchro)
                informTactic(info);
            } // VI
            else if (info.getInfoType() == Info.TYPE_VISUAL) {
                // first VI of the cycle?
                if (!this.synchro.wasSent()) {
                    informTactic(info);

                } else {
                    // if 2 VIs arrived set ViewMode to
                    // 75ms to get
                    // a VI in next Cycle (shouldn't
                    // occur to often...)
                    // don't act as we already used the
                    // first VI
                    try {
                        this.connection.send(new ChangeViewModeAction(
                                ControllerNumbers.VIEW_QUALITY.HIGH,
                                ControllerNumbers.VIEW_ANGLE.NARROW).toString());
                        AttentionToAction att = AttentionToActionFactory.getAction(world);
                        this.connection.send(att.toString());
                        this.connection.send(TurnNeckActionFactory.getAction(
                                world, world.getSelfRef().getLastState(),
                                synchro.getMainAction(),
                                synchro.getViewModeAction()).toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } // AURAL
            // CHANGE!!! do nothing as we act only after VIs (rw)
            // left CSI as they might trigger behaviour !? (rw)
            else if (// info.getType() == Info.TYPE_AURAL ||
                    //info.getInfoType() == Info.TYPE_AURAL_PSI ||
                    info.getInfoType() == Info.TYPE_AURAL_CSI) {
                // TODO consider VI arrivals of 300ms !? (rw)
                informTactic(info);
            }
        }
    }

    /**
     * This method delegates the updates of the worldmodel with given info, e.g.
     * the coachVI will be sent to the coachworld, and the VI will be sent to
     * the worldmodel
     *
     * @param info -
     *            a generic info
     */
    private void informWorld(Info info) {
        this.world.update(info);
    }

    /**
     * This method initializes all necessary data, reading it from the
     * properties given for that agent on startup.
     */
    @Override
    protected void initConfig() {
        String tmp = null;
        tmp = (String) this.config.get(GUI_CONNECT);
        if (tmp != null) {
            this.useGui = tmp.startsWith("true");
        }

        tmp = (String) this.config.get(STATES);
        if (tmp != null) {
            Vector<String> st = new Vector<String>();
            StringTokenizer strTok = new StringTokenizer(tmp);
            while (strTok.hasMoreElements()) {
                st.add(strTok.nextToken());
            }

            this.states = new String[st.size()];
            for (int i = 0; i < st.size(); i++) {
                this.states[i] = st.elementAt(i);
            }
        }
    }

    /**
     * This method informs the synchro about the arrival of a BS or VI info
     * arrived at client.
     *
     * @param info -
     *            the received info!
     */
    private void informSynchro(Info info) {
        if (info.getInfoType() == Info.TYPE_SENSE) {
            this.synchro.arrivedBS((SenseBodyInfo) info);
        }
        if (info.getInfoType() == Info.TYPE_VISUAL) {
            this.synchro.arrivedVI((VisualInfo) info);
        }
        if (info.getInfoType() == Info.TYPE_AURAL) {
            this.synchro.arrivedAural((AuralInfo) info);
        }
    }

    /**
     * This method calls the tactic to compute a new action by stateevaluation
     * and calling the action component!
     *
     * @param info -
     *            the recevied info!
     */
    private void informTactic(Info info) {
        if (info == null) {
            return;
        }
        if (info.getInfoType() == Info.TYPE_PTYPE) {
            if (this.mode == COACH) {
                this.tactic.receivedPlayerTypeInf((PlayerTypesInfo) info);
            }
        }
        if (info.getInfoType() == Info.TYPE_SENSE) {
            this.tactic.receivedSenseInfo();
        }
        if (info.getInfoType() == Info.TYPE_VISUAL) {
            this.tactic.receivedVisualInfo();
        }
        if (info.getInfoType() == Info.TYPE_AURAL || info.getInfoType() == Info.TYPE_AURAL_PSI || info.getInfoType() == Info.TYPE_AURAL_CSI) {
            this.tactic.receivedAuralInfo((AuralInfo) info);
        }

    }

    /**
     * This method sends a string to the server, usually the agents
     * actions. Additionally, the GUI is triggered after sending.
     *	@param actions - the actions, which should by sent to the
     *         server using the defined syntax.
     */
    @Override
    public void sendActions(String actions) {
        super.sendActions(actions);
        //if(!this.getWorld().isViBased())
        this.informGui();
    }

    /**
     * Informs soccerscope about the agents world model. This method is called
     * after sending so the visualized world is the one we based our action on.
     */
    private void informGui() {
        if (this.useGui && this.number > 0 && this.mode < 2) {
            TeamSoccerScopeMediator.registerInfoReceived(this);
            this.tactic.addStatesToGUI();
        }
    }

    // ~ --------------------------------------------
    // ~ Methods for accessing the agents type and location
    /**
     * Sets the player number
     *
     * @param num
     */
    public void setNumber(int num) {
        this.number = num;
    }

    /**
     * Retrieves the player number
     *
     * @return <code>this.number</code>
     */
    public int getNumber() {
        return this.number;
    }

    /**
     * Checks, if the player is goalie, by comparing the mode!
     *
     * @return true, if the agent is a goalie.
     */
    public boolean isGoalie() {
        return (this.mode == GOALIE);
    }

    /**
     * Retreives the complete worldmodel
     *
     * @return <code>this.world</code>
     */
    public WorldModel getWorld() {
        return this.world;
    }

    /**
     * Used by the tactic to inform the synchro that it
     * can begin sending.
     */
    public void informFinishedActing() {
        this.synchro.send();
    }

    /**
     * Returns the state list, which was given on startup to the
     * agent within the property file of the agent.
     * @return an array containing all states as string values together with
     *         used playmodes!
     */
    public String[] getStates() {
        return this.states;
    }

    /**
     * Retrieves the quarter of the cycle, in which the next VI is expected
     * to arrive. This is determined by the synchro.
     * @return the quarter as integer [0,3]
     */
    public int getVIArrivalQuarter() {
        return this.synchro.getVIArrivalQuarter();
    }

    /**
     * Agents main class, which is used to start an agent or a complete
     * team of agents with the coach. The expected call is :<br><br>
     *
     * java [-Dgui-connect=true|false]
     *       -jar
     *       &lt;dainamite.jar&gt;
     *       &lt;teamname&gt;
     *       &lt;server-ip&gt;
     *       &lt;property-file-1&gt;
     *       [&lt;property-file-2&gt;]
     *       ...<br><br>
     *
     * <ul>
     * <li> With <code>-Dgui-connect=true|false</code>, the usage of the Monitor
     * 		can be switched on or off globally. This value overrides all
     * 		settings regarding the useGui attribute of all given agent-property
     * 		files. This means, each agent started within the given JVM shares
     * 		the useGui attribute defined here.
     * </li>
     * <li> The above java-call expects that the binaries are included into
     * 		a jar-archive together with the manifest given in etc/jar/manifest.
     * 		There, the main class (this one) is mentioned together with the
     * 		dependent libraries, which have the be located in the subdirectory
     * 		specified there as well (currently lib/ssj.jar is the only
     * 		dependency).
     * </li>
     * <li> The teamname is also global, i.e. it overrides that defined in all
     *      agent property files. Hence it is not possible to start two team
     *      within the same JVM yet. However, an appropriate extension should
     *      be easy.
     * </li>
     * <li>	The server-ip is global as well.
     * </li>
     * <li>The last parameters should be the paths to the property files
     * 	   of the agents. Hence one agent is created for each specified
     *     path, whereas the order is important. At first, the goalie should
     *     be given, because it is assumed that this one has the tricot number 1,
     *     and this is given to the first agent, who connects to the server. After
     *     that, the player configurations can be added. The coach should be
     *     always given last (it is identified as coach, if the path contains
     *     "coach.conf"), because he tries to set the heterogeneous
     *     player-types. This may fail, if not all agents are connected to the
     *     server. Generally, any number of agents up to 11 plus coach can be
     *     started, as long as the defined order is kept. Note that it is
     *     sometimes necessary to give the absolute paths of the property-files
     *     in order to ensure, the system will find them (e.g. when the team is
     *     started remote).
     * </li>
     * </ul>
     *
     * @param args -
     *            must contain a team config-filename
     */
    public static void main(String[] args) {
        if (args.length < 3) {
            System.out.println("[RobocupAgent][MAIN] First parameter: Teamname");
            System.out.println("[RobocupAgent][MAIN] Second parameter: Host");
            System.out.println("[RobocupAgent][MAIN] Third and more parameters: Player confs");
            System.out.println("[RobocupAgent][MAIN] Exit Program!");
            System.exit(1);
        }

        /**
         * Reading global properties
         */
        String teamname = args[0];
        String host = args[1];
        String gui = System.getProperty(GUI_CONNECT);

        /**
         * Creating a new agent for each property-id agentX, where X is an
         * integer smaller 12 and greater 0, by loading the correpsonding
         * property file given as property value from the team-property file!
         * All global properties overrides local props for easier change!
         */

        // loading agents
        for (int i = 2; i < args.length; i++) {
            String file = args[i];

            // loading coach
            if (file.indexOf("coach.conf") >= 0) {

                try {
                    Thread.sleep(3000);
                } catch (Exception e) {
                }
                Properties props = loadProperties(file);
                if (teamname != null) {
                    props.put(TEAM_NAME, teamname);
                }
                if (host != null) {
                    props.put(HOST_NAME, host);
                }

                if (props != null) {
                    System.out.println("Successfully loaded properties from " + file);
                    CoachAgent agent = new CoachAgent(props);
                    agent.initialize();
                    agent.start();
                }
                continue;
            }

            if (file == null) {
                continue;
            }

            Properties props = loadProperties(file);

            if (props == null) {
                System.out.println("Failed to load properties from " + file);
                continue;
            }

            // update gloabl props:
            if (teamname != null) {
                props.put(TEAM_NAME, teamname);
            }
            if (host != null) {
                props.put(HOST_NAME, host);
            }
            if (gui != null) {
                props.put(GUI_CONNECT, gui);
            }

            System.out.println("Successfully loaded properties from " + file);
            RobocupAgent agent = new RobocupAgent(props);
            agent.initialize();
            agent.start();
        }
    }

    /**
     * This method loads the properties of an agent from a file with a given
     * filename. It is used to create a <code>Property</code> object out
     * of the given paths during startup of the agents (@see main()).
     *
     * @param fileName
     *            file with properties
     * @return Properties - loaded properties
     */
    public static Properties loadProperties(String fileName) {
        Properties p = new Properties();
        try {
            p.load(new FileInputStream(fileName));
        } catch (Exception e) {
            URL url = RobocupAgent.class.getClassLoader().getResource(fileName);
            if (url == null) {
                url = RobocupAgent.class.getClassLoader().getResource(
                        "etc/agents/" + fileName);
            }
            if (url == null) {
                e.printStackTrace();
                return null;
            }
            try {
                p.load(new FileInputStream(url.getFile()));
            } catch (Exception e1) {
                e1.printStackTrace();
                return null;
            }
        }

        return p;
    }

    /**
     * This method returns the last sent command to the SoccerServer
     *
     * @return - a string with the last command
     */
    public String getCommand() {
        return this.connection.getCommand();
    }

    /**
     * @return Returns the prophet.
     */
    public Prophet getProphet() {
        return this.prophet;
    }

    /**
     * Checks, whether the gui is used by the agent or not.
     * @return <code>this.useGui</code>
     */
    public boolean useGui() {
        return this.useGui;
    }

    /**
     * @return Returns the synchro.
     */
    public Synchro getSynchro() {
        return this.synchro;
    }
}
